// export abstract class DisplayObject {

//     x = 0;
//     y = 0;
//     alpha = 1;

//     draw(context: CanvasRenderingContext2D) {
//         context.globalAlpha = this.alpha;
//         this.render(context);
//     }

//     abstract render(context: CanvasRenderingContext2D): void

// }


// export class Bitmap extends DisplayObject {

//     source: string;

//     render(context: CanvasRenderingContext2D) {
//         const img = getImage(this.source);
//         if (img) {
//             context.drawImage(img, this.x, this.y);
//         }
//     }
// }

// export class Rectangle extends DisplayObject {

//     color = '#ff0000'
//     width = 70;
//     height = 70;

//     render(context: CanvasRenderingContext2D) {
//         context.save();
//         context.fillStyle = this.color;
//         context.fillRect(this.x, this.y, this.width, this.height);
//         context.restore();
//     }
// }


// export class TextField extends DisplayObject {


//     color = '#000000'
//     size = '';
//     text = '';

//     render(context: CanvasRenderingContext2D) {
//         context.save();
//         context.fillStyle = this.color;
//         context.font = this.size;
//         context.fillText(this.text, this.x, this.y + 20);
//         context.restore();
//     }
// }

// const store: any = {};



// function getImage(url: string) {
//     return store[url];
// }

// function loadMultiImage(list: string[], onSuccess: Function) {
//     let count = 0;
//     for (const url of list) {
//         loadImage(url, function onLoadItem(img) {
//             count++;
//             store[url] = img;
//             if (count === list.length) {
//                 onSuccess();
//             }
//         })
//     }
// }

// function loadImage(url: string, onSuccess: Function) {
//     const img = new Image();
//     //异步过程
//     img.src = url;
//     img.onload = function () {
//         onSuccess(img);
//     }
// }


// // export class GameEngine {

// //     onStart: Function;

// //     private displayObject: DisplayObject[] = [];

// //     start(images: string[], sceneConfig: any) {
// //         loadMultiImage(images, () => {
// //             this.loadScene(sceneConfig);
// //         });
// //     }

// //     addDisplayObject(displayObject: DisplayObject) {
// //         this.displayObject.push(displayObject)
// //     }

// //     render() {
// //         const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
// //         const context = canvas.getContext('2d');
// //         for (const displayObject of this.displayObject) {
// //             displayObject.draw(context);
// //         }
// //     }

// //     loadScene(sceneConfig: any) {
// //         for (let config of sceneConfig) {
// //             const object = createDisplayObject(config.type, config.properties);
// //             this.addDisplayObject(object);
// //         }
// //         this.render();
// //     }


// // }

// export class GameEngine {
//     objects: { [id: string]: DisplayObject } = {};

//     private displayObject: DisplayObject[] = [];

//     onStart?: () => void;
//     onUpdate?: (duringTime: number) => void;

//     start(images: string[], sceneConfig: any, onComplete?: Function) {
//         const initialTime = Date.now();
//         loadMultiImage(images, () => {
//             this.loadScene(sceneConfig);
//             if (this.onStart) {
//                 this.onStart();
//             }
//             const advancedTime = Date.now() - initialTime;
//             this.enterFrame(advancedTime);
//             if (onComplete) {
//                 onComplete();
//             }


//         });
//     }

//     private lastTime = 0;

//     enterFrame(advancedTime: number) {
//         const duringTime = advancedTime - this.lastTime;
//         this.lastTime = advancedTime;
//         this.onEnterFrame(duringTime);
//         requestAnimationFrame((advancedTime) => this.enterFrame(advancedTime));
//     }


//     private onEnterFrame(duringTime: number) {
//         const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
//         const context = canvas.getContext('2d');
//         context.clearRect(0, 0, canvas.width, canvas.height);
//         if (this.onUpdate) {
//             this.onUpdate(duringTime);
//         }
//         this.draw(context);
//     }

//     getDisplayObject(id: string) {
//         return this.objects[id];
//     }

//     addDisplayObject(displayObject: DisplayObject) {
//         this.displayObject.push(displayObject)
//     }

//     draw(context: CanvasRenderingContext2D) {
//         for (const displayObject of this.displayObject) {
//             displayObject.draw(context);
//         }
//     }

//     loadScene(sceneConfig: any) {
//         for (let config of sceneConfig) {
//             const object = createDisplayObject(config.type, config.properties);
//             if (config.id) {
//                 this.objects[config.id] = object;
//             }
//             this.addDisplayObject(object);
//         }
//     }


// }

// function createDisplayObject(type: string, properties: any): DisplayObject {
//     let displayObject: DisplayObject;
//     switch (type) {
//         case 'rectangle':
//             const rect = new Rectangle();
//             rect.color = properties.color|| '0x000000';
//             displayObject = rect;
//             break;
//         case 'textfield':
//             const textfield = new TextField();
//             textfield.color = properties.color || '0x000000';
//             textfield.text = properties.text || '';
//             textfield.size = properties.size ||'20px serif';
//             displayObject = textfield;
//             break;
//         case 'bitmap':
//             const bitmap = new Bitmap();
//             bitmap.source = properties.source || '';
//             displayObject = bitmap;

//     }
//     displayObject.x = properties.x || 0;
//     displayObject.y = properties.y || 0;
//     displayObject.alpha = properties.hasOwnProperty('alpha') ? properties.alpha : 1;
//     return displayObject;
// }
import { checkPointInRectangle, invertMatrix, Matrix, matrixAppendMatrix, Point, pointAppendMatrix, Rectangle } from "./math";

export abstract class DisplayObject {

    onClick: Function;
    x = 0;
    y = 0;
    scaleX = 1;
    scaleY = 1;
    rotation = 0;
    alpha = 1;
    globalAlpha = 1;
    parent: DisplayObject | null = null;
    globalMatrix: Matrix;
    localMatrix: Matrix;
    bounds: Rectangle = new Rectangle();
    draw(context: CanvasRenderingContext2D) {
        if (this.parent) {
            this.globalAlpha = this.parent.globalAlpha * this.alpha;
        }
        else {
            this.globalAlpha = this.alpha;
        }
        context.globalAlpha = this.globalAlpha;


        const localMatrix = new Matrix();
        localMatrix.updateFromDisplayObject(this.x, this.y, this.scaleX, this.scaleY, this.rotation);
        this.localMatrix = localMatrix;
        let globalMatrix: Matrix;

        if (this.parent) {
            const parentGlobalMatrix = this.parent.globalMatrix;
            globalMatrix = matrixAppendMatrix(localMatrix, parentGlobalMatrix);
        } else {
            globalMatrix = localMatrix;
        }
        this.globalMatrix = globalMatrix;

        context.setTransform(globalMatrix.a, globalMatrix.b, globalMatrix.c, globalMatrix.d, globalMatrix.tx, globalMatrix.ty);

        this.render(context);
    }

    abstract hitTest(point: Point): DisplayObject | null



    abstract render(context: CanvasRenderingContext2D): void

}

export class DisplayObjectContainer extends DisplayObject {

    children: DisplayObject[] = [];

    addChild(child: DisplayObject) {
        this.children.push(child);
        child.parent = this;
    }

    removeChild(child: DisplayObject) {
        const index = this.children.indexOf(child);
        if (index >= 0) {
            this.children.splice(index, 1);
        }
    }
    render(context: CanvasRenderingContext2D) {
        for (const displayobject of this.children) {
            displayobject.draw(context);
        }
    }

    hitTest(localThisPoint: Point) {

        for (let i = this.children.length - 1; i >= 0; i--) {
            const child = this.children[i];
            const childRelativeThisLocalMatrix = child.localMatrix;
            const invertChildRelativeThisLocalMatrix = invertMatrix(childRelativeThisLocalMatrix);
            const childLocalPoint = pointAppendMatrix(localThisPoint, invertChildRelativeThisLocalMatrix);
            const result = child.hitTest(childLocalPoint);
            if (result) {
                return result;
            }
        }
        return null;
    }
}


export class Bitmap extends DisplayObject {

    source: string;

    render(context: CanvasRenderingContext2D) {
        const img = getImage(this.source);
        if (img) {
            context.drawImage(img, 0, 0);
        }
    }

    hitTest(point: Point) {
        const img = getImage(this.source);
        if (img) {
            this.bounds.width = img.width;
            this.bounds.height = img.height;
        }
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}

export class RectangleShape extends DisplayObject {

    color = '#000000'
    width = 100;
    height = 100;

    render(context: CanvasRenderingContext2D) {
        context.save();
        context.fillStyle = this.color;
        context.fillRect(0, 0, this.width, this.height);
        context.restore();
    }

    hitTest(point: Point) {
        this.bounds.width = this.width;
        this.bounds.height = this.height;
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}


export class TextField extends DisplayObject {


    color = '#000000'
    text = '';

    render(context: CanvasRenderingContext2D) {
        context.save();
        context.fillStyle = this.color;
        context.fillText(this.text, 0, 20);
        context.restore();
    }

    hitTest(point: Point) {
        this.bounds.width = 100;
        this.bounds.height = 20;
        if (checkPointInRectangle(point, this.bounds)) {
            return this;
        }
        else {
            return null;
        }
    }
}

const store: any = {};



function getImage(url: string) {
    return store[url];
}

function loadMultiImage(list: string[], onSuccess: Function) {
    let count = 0;
    for (const url of list) {
        loadImage(url, function onLoadItem(img) {
            count++;
            store[url] = img;
            if (count === list.length) {
                onSuccess();
            }
        })
    }
}

function loadImage(url: string, onSuccess: Function) {
    const img = new Image();
    img.onload = function () {
        onSuccess(img);
    }
    //异步过程
    img.src = url;
}


export class GameEngine {
    objects: { [id: string]: DisplayObject } = {};

    private rootDisplayObject: DisplayObject;;

    onStart?: () => void;
    onUpdate?: (duringTime: number) => void;
    onTick?: () => void;

    getHitTestResult(point: Point) {
        const result = this.rootDisplayObject.hitTest(point);
        console.log(result);
        return result;
    }

    start(images: string[], sceneConfig: any, onComplete?: Function) {
        const initialTime = Date.now();
        loadMultiImage(images, () => {
            this.loadScene(sceneConfig);

            const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
            canvas.addEventListener('mousedown', (e) => {
                const point = new Point(e.offsetX, e.offsetY);
                const obj = this.getHitTestResult(point);
                if (obj && obj.onClick) {
                    obj.onClick();
                }

            })
            if (this.onStart) {
                this.onStart();
            }
            const advancedTime = Date.now() - initialTime;
            this.enterFrame(advancedTime);
            if (onComplete) {
                onComplete();
            }


        });
    }

    private lastTime = 0;


    enterFrame(advancedTime: number) {
        const duringTime = advancedTime - this.lastTime;
        this.lastTime = advancedTime;
        this.onEnterFrame(duringTime);
        requestAnimationFrame((advancedTime) => this.enterFrame(advancedTime));
    }


    private iterationCurrentTime = 0;

    private onEnterFrame(duringTime: number) {
        const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
        const context = canvas.getContext('2d');
        context.save();
        context.clearRect(0, 0, canvas.width, canvas.height);

        this.iterationCurrentTime += duringTime;
        const fps = 60;
        const mileSecondPerTick = 1000 / fps;
        while (this.iterationCurrentTime >= mileSecondPerTick) {
            this.iterationCurrentTime -= mileSecondPerTick;
            if (this.onTick) {
                this.onTick();
            }
        }
        if (this.onUpdate) {
            this.onUpdate(duringTime);
        }
        this.draw(context);
        context.restore();
    }

    getDisplayObject(id: string) {
        return this.objects[id];
    }


    draw(context: CanvasRenderingContext2D) {
        this.rootDisplayObject.draw(context);
    }

    loadScene(sceneConfig: any) {
        const object = this.createDisplayObject(sceneConfig.type, sceneConfig.properties, sceneConfig.children);
        this.rootDisplayObject = object;
    }

    private createDisplayObject(type: string, properties: any, children?: any[], id?: string): DisplayObject {
        let displayObject: DisplayObject;
        switch (type) {
            case 'rectangle':
                const rect = new RectangleShape();
                rect.color = properties.color || '0x000000';//老师您代码这里一直写的是“=”，应该改成“||”，不然方块永远都只能是黑色
                displayObject = rect;
                break;
            case 'textfield':
                const textfield = new TextField();
                textfield.color = properties.color || '0x000000';
                textfield.text = properties.text || '';
                displayObject = textfield;
                break;
            case 'bitmap':
                const bitmap = new Bitmap();
                bitmap.source = properties.source || '';
                displayObject = bitmap;
                break;
            case 'displayobjectcontainer':
                const container = new DisplayObjectContainer();
                if (children) {
                    for (const child of children) {
                        const childDisplayObject = this.createDisplayObject(child.type, child.properties, child.children, child.id);
                        container.addChild(childDisplayObject);
                    }
                }
                displayObject = container;
                break;

        }
        displayObject.x = properties.x || 0;
        displayObject.y = properties.y || 0;
        displayObject.rotation = properties.rotation || 0;
        displayObject.scaleX = properties.hasOwnProperty('scaleX') ? properties.scaleX : 1;
        displayObject.scaleY = properties.hasOwnProperty('scaleY') ? properties.scaleY : 1;
        displayObject.alpha = properties.hasOwnProperty('alpha') ? properties.alpha : 1;

        if (id) {
            this.objects[id] = displayObject;
        }
        return displayObject;
    }


}